/*
 * sample-Encoder-video.c
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/prctl.h>
#include <linux/videodev2.h>
#include <linux/v4l2-mediabus.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <imp/imp_log.h>
#include <imp/imp_isp.h>
#include <imp/imp_common.h>
#include <imp/imp_system.h>
#include <imp/imp_framesource.h>
#include <imp/imp_encoder.h>

#include "sample-common.h"

#define TAG "Sample-Encoder-video"

extern struct chn_conf chn[];
static int byGetFd = 0;

#define STREAM_OUT_PATH "/tmp/test_input.h264"
#define C_ALIGN(x,a)		(((x)+((a)-1))&~((a)-1))


static void *encoder_input_frame(void *args)
{
    int path_fd = 0;
    int ret = 0;
	int i = 0, frmSize = 0;
	int stream_out_fd = -1;
    IMPIspFastFrameAttr attr;

	IMPFrameInfo frame;
	IMPEncoderYuvOut stream;
	IMPEncoderYuvIn info;
	void *h = NULL;

	memset(&info, 0, sizeof(IMPEncoderYuvIn));
    memset((void *)&attr, 0 , sizeof(IMPIspFastFrameAttr));

	//encInfo
	info.type = PT_H264;
	info.mode = ENC_RC_MODE_SMART;
	info.frameRate = 25;
	info.gopLength = 50;
	info.initqp = -1;
	info.maxQp = 25;
	info.minQp = 51;
	info.staticTime = 2;
	info.maxBitRate = 1500;
	info.iBiasLvl = 0;
	info.changePos = 80;
	info.qualityLvl = 2;
	info.frmQPStep = 3;
	info.gopQPStep = 15;
	info.gopRelation = false;


    if (info.type != PT_H264) {
		printf("T23 not support h265\n");
		return -1;
	}

    attr.chn = 1;
    attr.width = 1920;
    attr.height = 1088;
    ret = IMP_ISP_GetFastFrame(0, &attr);
    if(ret != 0){
        IMP_LOG_ERR(TAG,"IMP_ISP_GetFastFrame failed\n");
        goto err;
    }


	frmSize = C_ALIGN(attr.width, 16) * C_ALIGN(attr.height, 16) * 3 / 2;
	stream.outAddr = (uint8_t *)malloc(frmSize);
	if(stream.outAddr == NULL) {
		printf("steamAddr malloc failed\n");
		return -1;
	}

	frame.width     = C_ALIGN(attr.width,16);
	frame.height    = C_ALIGN(attr.height,16);
	frame.size      = frmSize;
	frame.phyAddr   = attr.phyaddr;
	frame.virAddr   = attr.viraddr;

	ret = IMP_Encoder_YuvInit(&h, attr.width, attr.height, frmSize, (void *)&info);
	if ((ret < 0) || (h == NULL)) {
		printf("%d %d IMP_Encoder_YuvInit failed\n", (ret < 0), (h == NULL));
		return -1;
	}
    /*encoder stream output path*/
    stream_out_fd  = open(STREAM_OUT_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (stream_out_fd < 0) {
        printf("open out file:%s failed\n", STREAM_OUT_PATH);
        return -1;
    }


	for (i = 0; i < 10; i++) {
		ret = IMP_Encoder_YuvEncode(h, frmSize, frame, &stream);
		if (ret < 0) {
			printf("IMP_Encoder_YuvEncode failed\n");
			return -1;
		}

		printf("\r%d encode success", i);
		fflush(stdout);
		if (stream.outLen != write(stream_out_fd, stream.outAddr, stream.outLen)) {
			printf("stream write failed\n");
			return -1;
		}
        usleep(50*1000);
	}
	puts("");

    ret = IMP_ISP_ReleseFastFrame(0, &attr);
    if(ret != 0){
        IMP_LOG_ERR(TAG,"IMP_ISP_ReleseFastFrame failed\n");
    }
	free(stream.outAddr);
	IMP_Encoder_YuvExit(h);
	close(stream_out_fd);
err:
    pthread_exit(0);
}



int main(int argc, char *argv[])
{
	int i, ret;

	pthread_t input_tid;
    if (argc >= 2) {
        byGetFd = atoi(argv[1]);
    }

	/* Step.1 System init */
	ret = sample_system_init();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_System_Init() failed\n");
		return -1;
	}
	/* Step.2 FrameSource init */
	ret = sample_framesource_init();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "FrameSource init failed\n");
		return -1;
	}
    //get_frame();
	/* Step.3 Encoder init */
	for (i = 0; i < FS_CHN_NUM; i++) {
		if (chn[i].enable) {
			ret = IMP_Encoder_CreateGroup(chn[i].index);
			if (ret < 0) {
				IMP_LOG_ERR(TAG, "IMP_Encoder_CreateGroup(%d) error !\n", chn[i].index);
				return -1;
			}
		}
	}

	ret = sample_encoder_init();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "Encoder init failed\n");
		return -1;
	}

	/* Step.4 Bind */
	for (i = 0; i < FS_CHN_NUM; i++) {
		if (chn[i].enable) {
			ret = IMP_System_Bind(&chn[i].framesource_chn, &chn[i].imp_encoder);
			if (ret < 0) {
				IMP_LOG_ERR(TAG, "Bind FrameSource channel%d and Encoder failed\n",i);
				return -1;
			}
		}
	}

	/* Step.5 Stream On */
	ret = sample_framesource_streamon();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "ImpStreamOn failed\n");
		return -1;
	}

    ret = pthread_create(&input_tid, NULL, encoder_input_frame, NULL);
    if (ret < 0) {
        IMP_LOG_ERR(TAG, "Create ChnNum%d get_frame failed\n", chn[i].index);
        return -1;
    }

	/* Step.6 Get stream */
    if (byGetFd) {
        ret = sample_get_video_stream_byfd();
        if (ret < 0) {
            IMP_LOG_ERR(TAG, "Get video stream byfd failed\n");
            return -1;
        }
    } else {
        ret = sample_get_video_stream();
        if (ret < 0) {
            IMP_LOG_ERR(TAG, "Get video stream failed\n");
            return -1;
        }
    }


    pthread_join(input_tid,NULL);
	/* Exit sequence as follow */

	/* Step.a Stream Off */
	ret = sample_framesource_streamoff();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "FrameSource StreamOff failed\n");
		return -1;
	}

	/* Step.b UnBind */
	for (i = 0; i < FS_CHN_NUM; i++) {
		if (chn[i].enable) {
			ret = IMP_System_UnBind(&chn[i].framesource_chn, &chn[i].imp_encoder);
			if (ret < 0) {
				IMP_LOG_ERR(TAG, "UnBind FrameSource channel%d and Encoder failed\n",i);
				return -1;
			}
		}
	}

	/* Step.c Encoder exit */
	ret = sample_encoder_exit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "Encoder exit failed\n");
		return -1;
	}

	for (i = 0; i < FS_CHN_NUM; i++) {
		if (chn[i].enable) {
			ret = IMP_Encoder_DestroyGroup(chn[i].index);
			if (ret < 0) {
				IMP_LOG_ERR(TAG, "IMP_Encoder_CreateGroup(%d) error !\n", chn[i].index);
				return -1;
			}
		}
	}

	/* Step.d FrameSource exit */
	ret = sample_framesource_exit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "FrameSource exit failed\n");
		return -1;
	}

	/* Step.e System exit */
	ret = sample_system_exit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "sample_system_exit() failed\n");
		return -1;
	}

	return 0;
}
